package com.lzh.persevere.utils;

import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.util.StrUtil;
import com.lzh.persevere.config.enums.request.AppHttpCodeEnum;
import com.lzh.persevere.exception.LzhException;
import com.lzh.persevere.model.entity.FileInfo;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.util.*;

import static com.google.common.io.Files.getFileExtension;

/**
 * MinIO工具类
 *
 * @author luzhiheng
 */
@Slf4j
public class MinIOUtils {

    private static MinioClient minioClient;

    private static String endpoint;
    private static String bucketName;
    private static String accessKey;
    private static String secretKey;
    private static Long imgSize;
    private static Long fileSize;

    // 图片压缩质量(0.0-1.0)
    private static final float IMAGE_QUALITY = 0.2f;

    private static final String SEPARATOR = "/";

    public MinIOUtils() {
    }

    public MinIOUtils(String endpoint, String bucketName, String accessKey, String secretKey, Long imgSize, Long fileSize) {
        MinIOUtils.endpoint = endpoint;
        MinIOUtils.bucketName = bucketName;
        MinIOUtils.accessKey = accessKey;
        MinIOUtils.secretKey = secretKey;
        MinIOUtils.imgSize = imgSize;
        MinIOUtils.fileSize = fileSize;
        createMinioClient();
    }

    /**
     * 创建存储bucket
     *
     * @return Boolean
     */
    public static Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 文件上传
     *
     * @param file 文件
     * @return String
     */
    public static String upload(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        if (StrUtil.isBlank(originalFilename)) {
            throw new RuntimeException();
        }
        String fileName = UUID.randomUUID().toString().replace("-", "") + originalFilename.substring(originalFilename.lastIndexOf("."));
        String objectName = new DateUtil().format(new Date(), "yyyy-MM/dd") + "/" + fileName;
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectName;
    }

    /**
     * 文件上传
     *
     * @param file     文件
     * @param fileInfo 文件信息
     * @return String
     */
    public static String upload(MultipartFile file, FileInfo fileInfo) {
        String originalFilename = file.getOriginalFilename();
        if (StrUtil.isBlank(originalFilename)) {
            throw new RuntimeException();
        }
        String fileName = UUID.randomUUID().toString().replace("-", "") + originalFilename.substring(originalFilename.lastIndexOf("."));

        String uploadDirectory = new DateUtil().format(new Date(), "yyyy-MM/dd");
        String objectName = uploadDirectory + "/" + fileName;

        fileInfo.setUploadDirectory(uploadDirectory);
        fileInfo.setUploadName(fileName);
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectName;
    }

    /**
     * 上传文件到MinIO（支持图片自动压缩和格式优化）
     *
     * @param file     待上传的文件
     * @param fileInfo 文件信息实体（用于记录元数据）
     * @return 上传后的MinIO对象名（objectName）
     */
    public static String upload2(MultipartFile file, FileInfo fileInfo) {
        try {
            // 2. 处理文件名和存储路径
            String originalFilename = file.getOriginalFilename();
            String fileExt = getFileExtension(originalFilename); // 获取扩展名（如.jpg）
            String uuid = UUID.randomUUID().toString().replace("-", "");
            String uploadDirectory = new DateUtil().format(new Date(), "yyyy-MM/dd");
            String objectName = uploadDirectory + "/" + uuid + fileExt;

            // 3. 图片压缩和格式转换（仅对图片文件处理）
            InputStream uploadInputStream = file.getInputStream();
            // 压缩并转换为WebP（体积更小，兼容性较好）
            ByteArrayOutputStream compressedOut = new ByteArrayOutputStream();
            Thumbnails.of(uploadInputStream)
                    .outputFormat("jpg") // 强制转换为WebP
                    .outputQuality(IMAGE_QUALITY) // 压缩质量
                    .scale(1.0) // 保持原尺寸
                    .toOutputStream(compressedOut);
            uploadInputStream = new ByteArrayInputStream(compressedOut.toByteArray());
            // 更新扩展名和ContentType（因为转换为WebP）
            fileExt = ".jpg";
            objectName = uploadDirectory + "/" + uuid + fileExt;

            // 4. 完善FileInfo元数据
            fileInfo.setOriginName(originalFilename);
            fileInfo.setUploadDirectory(uploadDirectory);
            fileInfo.setUploadName(uuid + fileExt);
            fileInfo.setFileSize((long) uploadInputStream.available()); // 实际上传的文件大小（压缩后）

            // 5. 上传到MinIO（设置缓存头优化加载）
            PutObjectArgs objectArgs = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(uploadInputStream, uploadInputStream.available(), -1)
                    .contentType("image/webp".equals(fileExt) ? "image/webp" : file.getContentType())
                    .build();

            minioClient.putObject(objectArgs);
            return objectName;

        } catch (Exception e) {
            // 抛出自定义异常，便于上层统一处理
            throw new LzhException(500, "文件上传失败：" + e.getMessage());
        } finally {
            // 确保输入流关闭，避免资源泄露
            try {
                if (file.getInputStream() != null) {
                    file.getInputStream().close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 预览文件
     *
     * @param fileName
     * @return
     */
    public static String preview(String fileName) {
        // 查看文件地址
        GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(bucketName).object(fileName).method(Method.GET).build();
        try {
            String url = minioClient.getPresignedObjectUrl(build);
            return url;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 文件下载
     *
     * @param fileName 文件名称
     * @param res      response
     * @return Boolean
     */
    public static void download(String fileName, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName)
                .object(fileName).build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)) {
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
                while ((len = response.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                res.setCharacterEncoding("utf-8");
                // 设置强制下载不打开
                // res.setContentType("application/force-download");
                res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                try (ServletOutputStream stream = res.getOutputStream()) {
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 删除
     *
     * @param fileName
     * @return
     * @throws Exception
     */
    public static boolean remove(String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        } catch (Exception e) {
            return false;
        }
        return true;
    }


    /**
     * 创建基于Java端的MinioClient
     */
    public void createMinioClient() {
        try {
            if (null == minioClient) {
                log.info("开始创建 MinioClient...");
                minioClient = MinioClient
                        .builder()
                        .endpoint(endpoint)
                        .credentials(accessKey, secretKey)
                        .build();
                createBucket(bucketName);
                log.info("创建完毕 MinioClient...");
            }
        } catch (Exception e) {
            log.error("MinIO服务器异常：{}", e);
        }
    }

    /**
     * 获取上传文件前缀路径
     *
     * @return
     */
    public static String getBasisUrl() {
        return endpoint + SEPARATOR + bucketName + SEPARATOR;
    }

    /******************************  Operate Bucket Start  ******************************/

    /**
     * 启动SpringBoot容器的时候初始化Bucket
     * 如果没有Bucket则创建
     *
     * @throws Exception
     */
    private static void createBucket(String bucketName) throws Exception {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 判断Bucket是否存在，true：存在，false：不存在
     *
     * @return
     * @throws Exception
     */
    public static boolean bucketExists(String bucketName) throws Exception {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }


    /**
     * 获得Bucket的策略
     *
     * @param bucketName
     * @return
     * @throws Exception
     */
    public static String getBucketPolicy(String bucketName) throws Exception {
        String bucketPolicy = minioClient
                .getBucketPolicy(
                        GetBucketPolicyArgs
                                .builder()
                                .bucket(bucketName)
                                .build()
                );
        return bucketPolicy;
    }


    /**
     * 获得所有Bucket列表
     *
     * @return
     * @throws Exception
     */
    public static List<Bucket> getAllBuckets() throws Exception {
        return minioClient.listBuckets();
    }

    /**
     * 根据bucketName获取其相关信息
     *
     * @param bucketName
     * @return
     * @throws Exception
     */
    public static Optional<Bucket> getBucket(String bucketName) throws Exception {
        return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }

    /**
     * 根据bucketName删除Bucket，true：删除成功； false：删除失败，文件或已不存在
     *
     * @param bucketName
     * @throws Exception
     */
    public static boolean removeBucket(String bucketName) throws Exception {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /******************************  Operate Bucket End  ******************************/


    /******************************  Operate Files Start  ******************************/

    /**
     * 判断文件是否存在
     *
     * @param bucketName 存储桶
     * @param objectName 文件名
     * @return
     */
    public static boolean isObjectExist(String bucketName, String objectName) {
        boolean exist = true;
        try {
            minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }

    /**
     * 判断文件夹是否存在
     *
     * @param bucketName 存储桶
     * @param objectName 文件夹名称
     * @return
     */
    public static boolean isFolderExist(String bucketName, String objectName) {
        boolean exist = false;
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir() && objectName.equals(item.objectName())) {
                    exist = true;
                }
            }
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }

    /**
     * 根据文件前缀查询文件
     *
     * @param bucketName 存储桶
     * @param prefix     前缀
     * @param recursive  是否使用递归查询
     * @return MinioItem 列表
     * @throws Exception
     */
    public static List<Item> getAllObjectsByPrefix(String bucketName,
                                                   String prefix,
                                                   boolean recursive) throws Exception {
        List<Item> list = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
        if (objectsIterator != null) {
            for (Result<Item> o : objectsIterator) {
                Item item = o.get();
                list.add(item);
            }
        }
        return list;
    }

    /**
     * 获取文件流
     *
     * @param bucketName 存储桶
     * @param objectName 文件名
     * @return 二进制流
     */
    public static InputStream getObject(String bucketName, String objectName) throws Exception {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 断点下载
     *
     * @param bucketName 存储桶
     * @param objectName 文件名称
     * @param offset     起始字节的位置
     * @param length     要读取的长度
     * @return 二进制流
     */
    public InputStream getObject(String bucketName, String objectName, long offset, long length) throws Exception {
        return minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .offset(offset)
                        .length(length)
                        .build());
    }

    /**
     * 获取路径下文件列表
     *
     * @param bucketName 存储桶
     * @param prefix     文件名称
     * @param recursive  是否递归查找，false：模拟文件夹结构查找
     * @return 二进制流
     */
    public static Iterable<Result<Item>> listObjects(String bucketName, String prefix,
                                                     boolean recursive) {
        return minioClient.listObjects(
                ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(prefix)
                        .recursive(recursive)
                        .build());
    }

    /**
     * 使用MultipartFile进行文件上传
     *
     * @param bucketName  存储桶
     * @param file        文件名
     * @param objectName  对象名
     * @param contentType 类型
     * @return
     * @throws Exception
     */
    public static ObjectWriteResponse uploadFile(String bucketName, MultipartFile file,
                                                 String objectName, String contentType) throws Exception {
        InputStream inputStream = file.getInputStream();

        long size = file.getSize();
        if (size > imgSize * 1024 * 1024) {
            throw new LzhException(AppHttpCodeEnum.OVER_UPLOAD_LIMIT);
        }
        if (size > fileSize * 1024 * 1024) {
            throw new LzhException(AppHttpCodeEnum.OVER_UPLOAD_LIMIT);
        }

        return minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .contentType(contentType)
                        .stream(inputStream, inputStream.available(), -1)
                        .build());
    }

    /**
     * 上传本地文件
     *
     * @param bucketName 存储桶
     * @param objectName 对象名称
     * @param fileName   本地文件路径
     */
    public static ObjectWriteResponse uploadFile(String bucketName, String objectName,
                                                 String fileName) throws Exception {
        return minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .filename(fileName)
                        .build());
    }

    /**
     * 通过流上传文件
     *
     * @param bucketName  存储桶
     * @param objectName  文件对象
     * @param inputStream 文件流
     */
    public static ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) throws Exception {
        return minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .stream(inputStream, inputStream.available(), -1)
                        .build());
    }

    /**
     * 创建文件夹或目录
     *
     * @param bucketName 存储桶
     * @param objectName 目录路径
     */
    public static ObjectWriteResponse createDir(String bucketName, String objectName) throws Exception {
        return minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .stream(new ByteArrayInputStream(new byte[]{}), 0, -1)
                        .build());
    }

    /**
     * 获取文件信息, 如果抛出异常则说明文件不存在
     *
     * @param bucketName 存储桶
     * @param objectName 文件名称
     */
    public static String getFileStatusInfo(String bucketName, String objectName) throws Exception {
        return minioClient.statObject(
                StatObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build()).toString();
    }

    /**
     * 拷贝文件
     *
     * @param bucketName    存储桶
     * @param objectName    文件名
     * @param srcBucketName 目标存储桶
     * @param srcObjectName 目标文件名
     */
    public static ObjectWriteResponse copyFile(String bucketName, String objectName,
                                               String srcBucketName, String srcObjectName) throws Exception {
        return minioClient.copyObject(
                CopyObjectArgs.builder()
                        .source(CopySource.builder().bucket(bucketName).object(objectName).build())
                        .bucket(srcBucketName)
                        .object(srcObjectName)
                        .build());
    }

    /**
     * 删除文件
     *
     * @param bucketName 存储桶
     * @param objectName 文件名称
     */
    public static void removeFile(String bucketName, String objectName) throws Exception {
        minioClient.removeObject(
                RemoveObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
    }

    /**
     * 批量删除文件
     *
     * @param bucketName 存储桶
     * @param keys       需要删除的文件列表
     * @return
     */
    public static void removeFiles(String bucketName, List<String> keys) {
        List<DeleteObject> objects = new LinkedList<>();
        keys.forEach(s -> {
            objects.add(new DeleteObject(s));
            try {
                removeFile(bucketName, s);
            } catch (Exception e) {
                log.error("批量删除失败！error:{}", e);
            }
        });
    }

    /**
     * 获取文件外链
     *
     * @param bucketName 存储桶
     * @param objectName 文件名
     * @param expires    过期时间 <=7 秒 （外链有效时间（单位：秒））
     * @return url
     * @throws Exception
     */
    public static String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build();
        return minioClient.getPresignedObjectUrl(args);
    }

    /**
     * 获得文件外链
     *
     * @param bucketName
     * @param objectName
     * @return url
     * @throws Exception
     */
    public static String getPresignedObjectUrl(String bucketName, String objectName) throws Exception {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .method(Method.GET).build();
        return minioClient.getPresignedObjectUrl(args);
    }

    /**
     * 将URLDecoder编码转成UTF8
     *
     * @param str
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {
        String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
        return URLDecoder.decode(url, "UTF-8");
    }

    /******************************  Operate Files End  ******************************/


    /**
     * 下载MinIO桶中所有文件及文件夹，保持原始目录结构
     *
     * @param bucketName   桶名称
     * @param minioPrefix  MinIO中的前缀路径（可为空，下载整个桶）
     * @param localRootDir 本地根目录（下载后会在该目录下重建MinIO的目录结构）
     * @throws Exception 异常
     */
    public static void downloadAllFiles(String bucketName, String minioPrefix, String localRootDir) throws Exception {
        log.info("---------------开始下载---------------");
        // 规范化前缀，确保以斜杠结尾或为空
        String normalizedPrefix = (minioPrefix == null || minioPrefix.isEmpty())
                ? ""
                : (minioPrefix.endsWith("/") ? minioPrefix : minioPrefix + "/");
        // 确保本地根目录存在
        File rootDir = new File(localRootDir);
        if (!rootDir.exists()) {
            rootDir.mkdirs();
        }

        // 递归归下载所有对象
        recursiveDownload(minioClient, bucketName, normalizedPrefix, localRootDir);

        log.info("---------------下载完成！---------------");
    }

    /**
     * 递归下载对象，保持目录结构
     *
     * @param minioClient   MinIO客户端
     * @param bucketName    桶名称
     * @param currentPrefix 当前处理的前缀（MinIO中的路径）
     * @param localRootDir  本地根目录
     */
    private static void recursiveDownload(MinioClient minioClient, String bucketName, String currentPrefix, String localRootDir) throws Exception {
        // 列出当前前缀下的所有对象（不递归，手动处理子目录）
        ListObjectsArgs listArgs = ListObjectsArgs.builder()
                .bucket(bucketName)
                .prefix(currentPrefix)
                .recursive(false)
                .build();

        Iterable<Result<Item>> results = minioClient.listObjects(listArgs);

        for (Result<Item> result : results) {
            Item item = result.get();
            String objectName = item.objectName();

            // 跳过前缀本身（避免处理空目录时的循环）
            if (objectName.equals(currentPrefix)) {
                continue;
            }

            // 构建本地完整路径
            String localFullPath = localRootDir + File.separator + objectName;

            if (item.isDir() || objectName.endsWith("/")) {
                // 处理文件夹：创建本地目录并递归下载子内容
                File dir = new File(localFullPath);
                if (!dir.exists()) {
                    boolean created = dir.mkdirs();
                    if (!created) {
                        throw new Exception("无法创建本地目录: " + localFullPath);
                    }
                    log.info("创建目录: {}", localFullPath);
                }
                // 递归处理子目录
                recursiveDownload(minioClient, bucketName, objectName, localRootDir);
            } else {
                // 处理文件：下载到对应本地路径
                downloadSingleFile(minioClient, bucketName, objectName, localFullPath);
                log.info("下载文件: {}", localFullPath);
            }
        }
    }

    /**
     * 下载单个文件到指定路径
     */
    private static void downloadSingleFile(MinioClient minioClient, String bucketName, String objectName, String localFilePath) throws Exception {
        // 确保父目录存在
        File file = new File(localFilePath);
        File parentDir = file.getParentFile();
        if (!parentDir.exists()) {
            parentDir.mkdirs();
        }

        // 下载文件并写入本地
        try (InputStream in = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
             FileOutputStream out = new FileOutputStream(file)) {

            byte[] buffer = new byte[1024 * 8];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
    }
}
